package com.seal.commons.infrastructure.redis.configuration;

import com.seal.commons.exception.LimiterException;
import com.seal.commons.infrastructure.facade.distributed.limiter.DistributeLimiter;
import com.seal.commons.infrastructure.facade.distributed.limiter.DistributedRateLimiter;
import com.seal.commons.infrastructure.facade.distributed.limiter.LimitKeyType;
import com.seal.commons.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;


/**
 * 功能描述: <br/>
 *
 * @author yong.huang
 * @date: 2020-07-24 14:51<br/>
 * @since JDK 1.8
 */
@Slf4j
@Aspect
@Configuration
@ConditionalOnClass(DistributedRateLimiter.class)
@AutoConfigureAfter(DistributedAutoConfiguration.class)
public class DistributedLimiterAspectConfiguration {

    public static final String REDIS_SPLIT = ":";

    @Autowired
    private DistributedRateLimiter distributedRateLimiter;

    @Around("@annotation(limiter)")
    public Object around(ProceedingJoinPoint pjp, DistributeLimiter limiter) throws Throwable{
        String prefix = limiter.prefix();
        LimitKeyType keyType = limiter.limitKeyType();
        String key = "";
        if(keyType.equals(LimitKeyType.CUSTOM)){
            key = limiter.key();
        }
        if(keyType.equals(LimitKeyType.METHOD_NAME)){
            key = getKey(pjp);
        }
        if(StringUtils.isBlank(key)){
            throw new IllegalArgumentException("DistributeLimiter key is blank");
        }
        long period = limiter.period();
        long rate = limiter.rate();
        if(distributedRateLimiter.tryAcquire(prefix + REDIS_SPLIT + key, rate, period, limiter.timeUnit(), limiter.isCluster())){
            return pjp.proceed();
        }else{
            throw new LimiterException(String.format("limiter exception, rate:%d , period:%d ", rate, period));
        }
    }



    private String getKey(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        Signature sig = pjp.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = pjp.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        return target.getClass().getName() + REDIS_SPLIT + currentMethod.getName();
    }
}